/*
    Copyright (C) 2011 Samsung Electronics

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "config.h"

#if ENABLE(TIZEN_WEBKIT2_TILED_AC)

#include "SharedPixmapEfl.h"

#include "NotImplemented.h"

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <wtf/HashMap.h>
#include <wtf/OwnPtr.h>
#include <wtf/RefCounted.h>
#include <wtf/StdLibExtras.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <stdio.h>

static PFNEGLLOCKSURFACEKHRPROC eglLockSurfaceKHR = 0;
static PFNEGLUNLOCKSURFACEKHRPROC eglUnlockSurfaceKHR = 0;

namespace WebCore {
class PixmapContextEfl : public RefCounted<PixmapContextEfl> {
public:
    static PassRefPtr<PixmapContextEfl> create(bool isLockable, bool hasAlpha, bool hasDepth, bool hasStencil);
    virtual ~PixmapContextEfl();
    bool makeCurrent(EGLSurface);
    void* createSurface(int);
    void destroySurface(EGLSurface);
    bool lockSurface(EGLSurface);
    bool unlockSurface(EGLSurface);
    bool querySurface(EGLSurface, EGLint*);
    int createPixmap(int, int);
    void freePixmap(int);
    void* context() { return m_context; }
    static void HandleEGLError(const char* name);

private:
    PixmapContextEfl(bool isLockable, bool hasAlpha, bool hasDepth, bool hasStencil);
    virtual bool initialize();

protected:
    bool m_initialized;
    EGLConfig m_surfaceConfig;
    EGLDisplay m_display;
    EGLContext m_context;
    
    bool m_isLockable;
    bool m_hasAlpha;
    bool m_hasDepth;
    bool m_hasStencil;
};

Display* g_nativeDisplay;
int g_nativeWindow;
class PixmapContextPool {
protected:
    PixmapContextPool(void);
    virtual ~PixmapContextPool();
public:
    static inline PixmapContextPool& getInstance()
    {
        static PixmapContextPool pixmapContextPool;
        return pixmapContextPool;
    }

    PixmapContextEfl* getContext(bool isLockable, bool hasAlpha, bool hasDepth, bool hasStencil);

private:
    typedef HashMap<int, RefPtr<PixmapContextEfl> > PixmapContextMap;
    PixmapContextMap m_pixmapContexts;
};

PixmapContextPool::PixmapContextPool()
{
    if (!g_nativeDisplay)
        g_nativeDisplay = XOpenDisplay(0);
    if (!g_nativeWindow)
        g_nativeWindow = XCreateSimpleWindow(g_nativeDisplay, XDefaultRootWindow(g_nativeDisplay),
                            0, 0, 1, 1, 0,
                            BlackPixel(g_nativeDisplay, 0), WhitePixel(g_nativeDisplay, 0));
    XFlush(g_nativeDisplay);
}

PixmapContextPool::~PixmapContextPool()
{
    PixmapContextMap::iterator end = m_pixmapContexts.end();
    for (PixmapContextMap::iterator iter = m_pixmapContexts.begin(); iter != end; ++iter) {
        RefPtr<PixmapContextEfl> context = iter->second;
        context.release();
    }
    m_pixmapContexts.clear();

    if (g_nativeWindow) {
        XDestroyWindow(g_nativeDisplay, g_nativeWindow);
        g_nativeWindow = 0;
    }
}

PixmapContextEfl* PixmapContextPool::getContext(bool isLockable, bool hasAlpha, bool hasDepth, bool hasStencil)
{
    int contextId = ((isLockable) | (hasAlpha << 1) | (hasDepth << 2) | (hasStencil << 3));
    RefPtr<PixmapContextEfl> pixmapContext = m_pixmapContexts.get(contextId);
    if (!pixmapContext) {
        pixmapContext = PixmapContextEfl::create(isLockable, hasAlpha, hasDepth, hasStencil);
        if (pixmapContext)
            m_pixmapContexts.add(contextId, pixmapContext);
        else
            return 0;
    }
    return pixmapContext.get();
}

PassRefPtr<PixmapContextEfl> PixmapContextEfl::create(bool isLockable, bool hasAlpha, bool hasDepth, bool hasStencil)
{
    RefPtr<PixmapContextEfl> context = adoptRef(new PixmapContextEfl(isLockable, hasAlpha, hasDepth, hasStencil));
    if (!context->initialize())
        return 0;
    return context.release();
}

void PixmapContextEfl::HandleEGLError(const char* name)
{
    static const char* const egErrorStrings[] =
    {
        "EGL_SUCCESS",
        "EGL_NOT_INITIALIZED",
        "EGL_BAD_ACCESS",
        "EGL_BAD_ALLOC",
        "EGL_BAD_ATTRIBUTE",
        "EGL_BAD_CONFIG",
        "EGL_BAD_CONTEXT",
        "EGL_BAD_CURRENT_SURFACE",
        "EGL_BAD_DISPLAY",
        "EGL_BAD_MATCH",
        "EGL_BAD_NATIVE_PIXMAP",
        "EGL_BAD_NATIVE_WINDOW",
        "EGL_BAD_PARAMETER",
        "EGL_BAD_SURFACE"
    };

    EGLint errorCode = eglGetError();

    printf("'%s' returned egl error '%s' (0x%x)\n",
              name,
              egErrorStrings[errorCode - EGL_SUCCESS],
              errorCode);
}

PixmapContextEfl::PixmapContextEfl(bool isLockable, bool hasAlpha, bool hasDepth, bool hasStencil)
    : m_initialized(false)
    , m_display(EGL_NO_DISPLAY)
    , m_context(EGL_NO_CONTEXT)
    , m_isLockable(isLockable)
    , m_hasAlpha(hasAlpha)
    , m_hasDepth(hasDepth)
    , m_hasStencil(hasStencil)
{
}

PixmapContextEfl::~PixmapContextEfl()
{
    if (m_context) {
        eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        eglDestroyContext(m_display, m_context);
        m_context = EGL_NO_CONTEXT;
    }
    m_display = 0;
}

bool PixmapContextEfl::initialize()
{
    EGLint major, minor;
    EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };

    m_display = eglGetDisplay(g_nativeDisplay);
    if (m_display == EGL_NO_DISPLAY)
        HandleEGLError("eglGetDisplay");

    if (eglInitialize(m_display, &major, &minor) != EGL_TRUE)
        HandleEGLError("eglInitialize");

    EGLint configCount = 0;
    int i = 0;
    EGLint configAttribs[32];
    configAttribs[i++] = EGL_LEVEL;
    configAttribs[i++] = 0;
    // FIXME : Create pixmap surface with RGB_565 when alpha is off. Graphics driver needs to be fixed first.
    configAttribs[i++] = EGL_RED_SIZE;
    configAttribs[i++] = 8;
    configAttribs[i++] = EGL_GREEN_SIZE;
    configAttribs[i++] = 8;
    configAttribs[i++] = EGL_BLUE_SIZE;
    configAttribs[i++] = 8;
    configAttribs[i++] = EGL_ALPHA_SIZE;
    configAttribs[i++] = 8;

    if (m_isLockable) {
        configAttribs[i++] = EGL_MATCH_FORMAT_KHR;
        configAttribs[i++] = EGL_FORMAT_RGBA_8888_EXACT_KHR;
        configAttribs[i++] = EGL_SURFACE_TYPE;
        configAttribs[i++] = EGL_PIXMAP_BIT | EGL_LOCK_SURFACE_BIT_KHR;
        configAttribs[i++] = EGL_DEPTH_SIZE;
        configAttribs[i++] = 0;
        configAttribs[i++] = EGL_STENCIL_SIZE;
        configAttribs[i++] = 0;
    } else {
        configAttribs[i++] = EGL_SURFACE_TYPE;
        configAttribs[i++] = EGL_PIXMAP_BIT;
        configAttribs[i++] = EGL_DEPTH_SIZE;
        if (m_hasDepth)
            configAttribs[i++] = 16;
        else
            configAttribs[i++] = 0;
        configAttribs[i++] = EGL_STENCIL_SIZE;
        if (m_hasStencil)
            configAttribs[i++] = 8;
        else
            configAttribs[i++] = 0;
    }
    configAttribs[i++] = EGL_RENDERABLE_TYPE;
    configAttribs[i++] = EGL_OPENGL_ES2_BIT;
    configAttribs[i++] = EGL_NONE;

    if (eglChooseConfig(m_display, configAttribs, &m_surfaceConfig, 1, &configCount) != EGL_TRUE) {
        HandleEGLError("eglChooseConfig");
        return false;
    }
    m_context = eglCreateContext(m_display, m_surfaceConfig, EGL_NO_CONTEXT, contextAttribs);
    if( m_context == EGL_NO_CONTEXT) {
        HandleEGLError("eglCreateContext");
        return false;
    }
    return true;
}

bool PixmapContextEfl::makeCurrent(EGLSurface surface)
{
    if (eglMakeCurrent(m_display, surface, surface, m_context) != EGL_TRUE) {
        HandleEGLError("eglMakeCurrent");
        return false;
    }
    return true;
}

void* PixmapContextEfl::createSurface(int pixmapID)
{
    EGLSurface surface = eglCreatePixmapSurface(m_display, m_surfaceConfig, pixmapID, NULL);
    if (surface == EGL_NO_SURFACE)
        HandleEGLError("eglCreatePixmapSurface");

    return surface;
}

void PixmapContextEfl::destroySurface(EGLSurface surface)
{
    if (surface) {
        if (eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE)
            HandleEGLError("eglMakeCurrent");
        eglDestroySurface(m_display, surface);
    }
}

bool PixmapContextEfl::lockSurface(EGLSurface surface)
{
    EGLint lockAttrib[] = { EGL_LOCK_USAGE_HINT_KHR, EGL_WRITE_SURFACE_BIT_KHR, EGL_NONE };
    if (!eglLockSurfaceKHR)
        eglLockSurfaceKHR = (PFNEGLLOCKSURFACEKHRPROC)eglGetProcAddress("eglLockSurfaceKHR");
    if (eglLockSurfaceKHR(m_display, surface, lockAttrib) != EGL_TRUE) {
        HandleEGLError("eglLockSurfaceKHR");
        return false;
    }
    return true;
}

bool PixmapContextEfl::unlockSurface(EGLSurface surface)
{
    if (!eglUnlockSurfaceKHR)
        eglUnlockSurfaceKHR = (PFNEGLUNLOCKSURFACEKHRPROC)eglGetProcAddress("eglUnlockSurfaceKHR");
    if (eglUnlockSurfaceKHR(m_display, surface) != EGL_TRUE) {
        HandleEGLError("eglUnlockSurfaceKHR");
        return false;
    }
    return true;
}

bool PixmapContextEfl::querySurface(EGLSurface surface, EGLint* value)
{
    if (eglQuerySurface(m_display, surface, EGL_BITMAP_POINTER_KHR, value) != EGL_TRUE) {
        HandleEGLError("eglQuerySurface");
        return false;
    }
    return true;
}

int PixmapContextEfl::createPixmap(int width, int height)
{
    // FIXME : Create 16 bit pixmap when alpha is off. Graphics driver needs to be fixed first.
    return XCreatePixmap(g_nativeDisplay, g_nativeWindow, width, height, 32);
}

void PixmapContextEfl::freePixmap(int pixmap)
{
    if (pixmap)
        XFreePixmap(g_nativeDisplay, pixmap);
}

PassOwnPtr<SharedPixmapEfl> SharedPixmapEfl::create(int width, int height, bool lockable, bool hasAlpha, bool hasDepth, bool hasStencil)
{
    OwnPtr<SharedPixmapEfl> pixmap = adoptPtr(new SharedPixmapEfl(width, height, lockable, hasAlpha, hasDepth, hasStencil));
    if (!pixmap->initialize())
        return nullptr;
    return pixmap.release();
}

SharedPixmapEfl::SharedPixmapEfl(int width, int height, bool lockable, bool hasAlpha, bool hasDepth, bool hasStencil)
    : m_width(width)
    , m_height(height)
    , m_pixmap(0)
    , m_surface(0)
    , m_isLockable(lockable)
    , m_hasAlpha(hasAlpha)
    , m_hasDepth(hasDepth)
    , m_hasStencil(hasStencil)
    , m_isUsed(false)
{
    m_pixmapContext = 0;
}

SharedPixmapEfl::~SharedPixmapEfl()
{
    if (m_surface != EGL_NO_SURFACE) {
        m_pixmapContext->destroySurface(m_surface);
        m_surface = EGL_NO_SURFACE;
    }
    if (m_pixmap) {
        m_pixmapContext->freePixmap(m_pixmap);
        m_pixmap = 0;
    }
}

bool SharedPixmapEfl::initialize()
{
    if (m_width <= 0 || m_height <= 0)
        return false;

    PixmapContextPool& pixmapContextPool = PixmapContextPool::getInstance();
    m_pixmapContext = pixmapContextPool.getContext(m_isLockable, m_hasAlpha, m_hasDepth, m_hasStencil);

    if(!m_pixmapContext)
        return false;

    m_pixmap = m_pixmapContext->createPixmap(m_width, m_height);
    if (!m_pixmap)
        return false;
    m_surface = (void*)(m_pixmapContext->createSurface(m_pixmap));
    if (m_surface == EGL_NO_SURFACE)
        return false;
    return true;
}

void* SharedPixmapEfl::context()
{
    return m_pixmapContext->context();
}

bool SharedPixmapEfl::makeContextCurrent()
{
    return m_pixmapContext->makeCurrent(m_surface);
}

bool SharedPixmapEfl::lockSurface()
{
    return m_pixmapContext->lockSurface(m_surface);
}

bool SharedPixmapEfl::unlockSurface()
{
    return m_pixmapContext->unlockSurface(m_surface);
}

bool SharedPixmapEfl::querySurface(int* value)
{
    int* value_local;
    bool ret;

    ret = m_pixmapContext->querySurface(m_surface, (EGLint*)&value_local);
    *value = (int)value_local;

    return ret;
}
}
#endif // ENABLE(TIZEN_ACCELERATED_COMPOSITING)

